/**
 * 服务器进程配置
 *
 * Created by snowingsea on 2020/01/20.
 */

const cors = require('cors');
const fs = require('fs-extra');
const http = require('http');
const express = require('express');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid');
const debug = require('debug')('phoenix:server');
const routes = require('./routes');
const mongoClient = require('./models/mongo/client');
// const redis = require('./models/redis');
const thriftClient = require('./models/hydra/thrift-client');
const pager = require('./models/pager');
const PhoenixError = require('./models/phoenix-error');
const utility = require('./models/utility');
const logger = require('./models/logger');
const numbers = require('./models/numbers');
const cls = require('./models/cls');
const { MessagePublisher } = require('./models/amqp/pub-sub/publisher');
const ipc = require('./models/amqp/ipc');
/**
 * 服务类
 */
class Server {
  constructor() {
    this.phoenix = express();
    this.port = utility.port();

    // 连接数据库
    mongoClient.connect().then(() => {}).catch(() => {});
    // redis.connect().then(() => {}).catch(() => {});
    MessagePublisher.createWebsocketMessageBus().then(() => {}).catch(() => {});
    ipc.initIpcPublisher().then(() => {}).catch(() => {});
    ipc.initIpcSubscriber().then(() => {}).catch(() => {});

    // Thrift
    thriftClient.connect().then(() => {}).catch(() => {});
    // 设置端口
    this.phoenix.set('port', this.port);
    // 异常处理
    this.phoenix.use(this.handleException.bind(this));
    // CORS
    this.phoenix.use(cors({ origin: '*' }));
    // 配置
    this.phoenix.use(bodyParser.json({ limit: '50mb' }));
    this.phoenix.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));

    this.phoenix.use((req, res, next) => {
      cls.createRequestContext({ id: req.query.uuid || uuidv4() });
      next();
    });
    // 日志
    this.phoenix.use(logger.httpLogFormat());
    this.phoenix.use(cookieParser());

    this.phoenix.set('trust proxy', true);

    // 清除锁逻辑
    this.phoenix.use((req, res, next) => {
      const { send } = res;
      res.send = async function fn(body) {
        if (req.$lockKey) {
          await numbers.releaseLock(req.$lockKey);
        }
        send.call(this, body);
      };
      next();
    });
    // 路由配置
    this.phoenix.use(express.Router());
    routes(this.phoenix);
    // 错误处理
    this.phoenix.use(Server.handle404Error);
    this.phoenix.use(Server.handleError);

    // 创建服务进程
    this.server = http.createServer(this.phoenix);
    const webSocketServer = new WebSocket.Server({ server: this.server });
    webSocketServer.on('connection', pager.connect);
    this.server.listen(this.port, '0.0.0.0');
    this.server.setTimeout(global.config.timeout);
    this.server.on('error', Server.onError);
    this.server.on('listening', () => {
      const address = this.server.address();
      const bind = (typeof address === 'string') ? `pipe ${address}` : `port ${address.port}`;
      debug(`Phoenix started in ${this.phoenix.get('env')} mode on ${bind}`);
    });
  }

  /**
   * 使用域处理服务器异常
   * @param req
   * @param res
   * @param next
   */
  handleException(req, res, next) {
    const domain = require('domain').create();
    // handle errors on this domain
    domain.on('error', (err) => {
      logger.error('DOMAIN ERROR CAUGHT', err);
      try {
        // failsafe shutdown in 5 seconds
        setTimeout(() => {
          logger.error('Failsafe shutdown.');
          process.exit(1);
        }, 5000);

        // disconnect from the cluster
        const { worker } = require('cluster');
        if (worker) worker.disconnect();

        // stop taking new requests
        if (this.server) {
          this.server.close();
        }

        try {
          // attempt to use Express error route
          next(err);
        } catch (e) {
          // if Express error route failed, try
          // plain Node response
          logger.error('Express error mechanism failed.', e);
          res.statusCode = 500;
          res.json({
            request: req.url,
            message: 'Server error',
          });
        }
      } catch (e) {
        logger.error('Unable to send 500 response.', e);
      }
    });

    // add the request and response objects to the domain
    domain.add(req);
    domain.add(res);

    // execute the rest of the request chain in the domain
    domain.run(next);
  }

  /**
   * 处理404错误
   * @param req
   * @param res
   * @param next
   */
  static handle404Error(req, res, next) {
    const err = new PhoenixError('not found');
    next(err);
  }

  /**
   * 错误处理
   * @param err
   * @param req
   * @param res
   * @param next
   */
  static handleError(err, req, res, next) { // eslint-disable-line no-unused-vars
    res.status(err.status || 500);
    const resError = {
      request: req.url,
      message: err.message,
    };
    if (req.app.get('env') === 'development' && err.description) {
      resError.description = err.description;
    }

    const { pathname } = req._parsedUrl; // eslint-disable-line no-underscore-dangle
    if (pathname.endsWith('.json')) {
      res.json(resError);
    } else {
      res.json(resError);
    }

    if (req.file) {
      fs.unlink(req.file.path, () => {});
    }
    if (res.statusCode === 500) {
      logger.error(
        {
          httpPath: req.url,
          httpMethod: req.method,
          statusCode: res.statusCode,
        },
        err
      );
    }
  }

  /**
   * 服务器进程错误消息处理
   * @param err
   */
  static onError(err) {
    if (err.syscall !== 'listen') {
      throw err;
    }

    const port = utility.port();
    const bind = (typeof port === 'string') ? `Pipe ${port}` : `Port ${port}`;

    // handle specific listen errors with friendly messages
    switch (err.code) {
      case 'EACCES':
        logger.error(`${bind} requires elevated privileges`);
        process.exit(1);
      // eslint-disable-next-line no-fallthrough
      case 'EADDRINUSE':
        logger.error(`${bind} is already in use`);
        process.exit(1);
      // eslint-disable-next-line no-fallthrough
      default:
        throw err;
    }
  }
}

/**
 * 创建服务器进程
 */
function createServer() {
  return new Server();
}

/**
 * Export model definition object.
 */
module.exports = createServer;
